import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.beans.MutablePropertyValues
import com.kyub.dynamoscaffold.DynaViewResolver
import com.kyub.dynamoscaffold.DynaTemplateGenerator
import org.codehaus.groovy.grails.scaffolding.DefaultGrailsTemplateGenerator
import org.springframework.beans.factory.config.BeanDefinition
import org.codehaus.groovy.grails.commons.GrailsControllerClass
import org.codehaus.groovy.grails.commons.GrailsClassUtils
import org.springframework.context.ApplicationContext
import org.codehaus.groovy.grails.scaffolding.GrailsTemplateGenerator
import org.codehaus.groovy.grails.commons.GrailsDomainClass
import org.codehaus.groovy.grails.plugins.DefaultGrailsPlugin
import org.apache.commons.logging.LogFactory
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.codehaus.groovy.grails.scaffolding.view.ScaffoldingViewResolver
import com.kyub.dynamoscaffold.ExtraPropertyEditorRegistrar

class DynamoscaffoldGrailsPlugin {
    // the plugin version
    def version = "0.5"
    // the version or versions of Grails the plugin is designed for
    def grailsVersion = "1.3.1 > *"
    // the other plugins this plugin depends on
    def dependsOn = [:]
    // resources that are excluded from plugin packaging
    def pluginExcludes = [
            "grails-app/views/error.gsp"
    ]

    def observe = ['controllers']
    def loadAfter = ['controllers']

   static final LOG  = LogFactory.getLog(DefaultGrailsPlugin)




    def author = "Sam Reghenzi"
    def authorEmail = "sam at kyub dot com"
    def title = "DynamoScaffold"
    def description = '''\\
Dynamic declarative scaffolding for grails
'''

    // URL to the plugin's documentation
    def documentation = "http://code.google.com/p/dynamoscaffold/wiki/QuickStart"

    def doWithWebDescriptor = { xml ->
       
    }

    def doWithSpring = {

        log.info("registro viewResolver")

        dynaControllerRowActionsMap(HashMap)

        dynaControllerListItemsMap(HashMap)

        dynaControllerInlinesMap(HashMap)

        dynaControllerShowItemsMap(HashMap)

         dynaControllerSearchItemsMap(HashMap)

        dynaScaffoldingTemplateGenerator(DynaTemplateGenerator, ref("classLoader")){

              dynaControllerRowActionsMap = dynaControllerRowActionsMap

              dynaControllerListItemsMap = dynaControllerListItemsMap

              dynaControllerInlinesMap = dynaControllerInlinesMap

              dynaControllerShowItemsMap = dynaControllerShowItemsMap

              dynaControllerSearchItemsMap = dynaControllerSearchItemsMap

          
        }

      dynaControllerToScaffoldedDomainClassMap(HashMap)
      dynaScaffoldedActionMap(HashMap)
      dynaControllerRowActionsMap(HashMap)


      BeanDefinition beanDef = getBeanDefinition("jspViewResolver")

       dynaViewResolver(DynaViewResolver){
            if(beanDef) {
                for(PropertyValue pv in beanDef.getPropertyValues().getPropertyValueList()) {
                    delegate."${pv.name}" = pv.value
                }
            }
            templateGenerator = dynaScaffoldingTemplateGenerator
            scaffoldedActionMap = ref("dynaScaffoldedActionMap")
            scaffoldedDomains = ref("dynaControllerToScaffoldedDomainClassMap")


       }

      extraEditorRegistrar(ExtraPropertyEditorRegistrar)




    }


   def doWithApplicationContext = { ApplicationContext appCtx ->
        for(GrailsControllerClass controllerClass in application.controllerClasses) {
            configureScaffoldingController(appCtx, application, controllerClass)
        }
    }

    def onChange = { event ->
      println("DynamScaffold: RELOADING DYNAMIC CONTROLLERS")
        if (application.isControllerClass(event.source)) {
            def controller =  application.controllerClasses.find { it.clazz == event.source } 
            configureScaffoldingController(application.getMainContext(), application, controller)
        }
       println("DynamScaffold: CLEARING VIEW CACHE")
        ScaffoldingViewResolver.clearViewCache()
        // inutile DynaViewResolver.clearViewCache()
        println(ScaffoldingViewResolver.scaffoldedViews)
       println("DynamScaffold: RELOADING COMPLETE")
    }

    def onConfigChange = { event ->

    }

    private configureScaffoldingController(ApplicationContext appCtx, GrailsApplication application, GrailsControllerClass controllerClass) {
        GrailsTemplateGenerator generator = appCtx.getBean("dynaScaffoldingTemplateGenerator")
        GroovyClassLoader parentLoader = appCtx.getBean("classLoader")
        Map scaffoldedActionMap = appCtx.getBean("dynaScaffoldedActionMap")
        Map scaffoldedDomains = appCtx.getBean("dynaControllerToScaffoldedDomainClassMap")
        Map rowActionsMap  = appCtx.getBean("dynaControllerRowActionsMap")
        Map listItemsMap = appCtx.getBean("dynaControllerListItemsMap")
        Map inlinesMap = appCtx.getBean("dynaControllerInlinesMap")
        Map showItemsMap = appCtx.getBean("dynaControllerShowItemsMap")
        Map searchItemsMap = appCtx.getBean("dynaControllerSearchItemsMap")



        def javaClass = controllerClass.clazz
        def metaClass = javaClass.metaClass



        def scaffoldProperty = readControllerProperty(controllerClass,"dynamoScaffold")
        if (scaffoldProperty) {
            
            GrailsDomainClass domainClass = getScaffoldedDomainClass(application, controllerClass, scaffoldProperty)
            
           scaffoldedActionMap[controllerClass.logicalPropertyName] = []
            if (domainClass) {
                scaffoldedDomains[controllerClass.logicalPropertyName] = domainClass



                List rowActions =  readControllerProperty(controllerClass,"rowActions")
                if(rowActions){
                    rowActionsMap[domainClass.logicalPropertyName] = rowActions
                }
                List listItems =  readControllerProperty(controllerClass,"listItems")
                if(listItems){
                    listItemsMap[domainClass.logicalPropertyName] = listItems
                }
                List inlines =  readControllerProperty(controllerClass,"inlines")
                if(inlines){
                    inlinesMap[domainClass.logicalPropertyName] = inlines

                }

                List showItems =  readControllerProperty(controllerClass,"showItems")
                if(showItems){
                   showItemsMap[domainClass.logicalPropertyName] = showItems
                }
                List searchItems = readControllerProperty(controllerClass,"searchItems")
                if(searchItems){
                  searchItemsMap[domainClass.logicalPropertyName] = searchItems
                }


                String controllerSource = generateControllerSource(generator, domainClass)
                def scaffoldedInstance = createScaffoldedInstance(parentLoader, controllerSource)
                List actionProperties = getScaffoldedActions(scaffoldedInstance)
                for (MetaProperty actionProp in actionProperties) {
                    if (actionProp) {
                        String propertyName = actionProp.name
                        def mp = metaClass.getMetaProperty(propertyName)
                        scaffoldedActionMap[controllerClass.logicalPropertyName] << propertyName

                        if (!mp) {
                            Closure propertyValue = actionProp.getProperty(scaffoldedInstance)
                            metaClass."${GrailsClassUtils.getGetterName(propertyName)}" = {->
                                propertyValue.delegate = delegate
                                propertyValue.resolveStrategy = Closure.DELEGATE_FIRST
                                propertyValue
                            }
                            controllerClass.registerMapping(propertyName)
                            if(propertyName == GrailsControllerClass.INDEX_ACTION) {
                                controllerClass.defaultActionName = propertyName
                            }
                        }
                    }
                }
            }
            else {
                LOG.error "Cannot generate controller logic for scaffolded class ${scaffoldProperty}. It is not a domain class!"
            }
        }
    }

  private def readControllerProperty(GrailsControllerClass controllerClass,String propertyName) {
    return GrailsClassUtils.getPropertyOrStaticPropertyOrFieldValue(controllerClass.getReference().getWrappedInstance(), propertyName)
  }

  private GrailsDomainClass getScaffoldedDomainClass(application, GrailsControllerClass controllerClass, scaffoldProperty) {
        GrailsDomainClass domainClass = null

        if (scaffoldProperty) {
            if (scaffoldProperty instanceof Class) {
                domainClass = application.getDomainClass(scaffoldProperty.name)
            }
            else if (scaffoldProperty) {
                scaffoldProperty = controllerClass.packageName ? "${controllerClass.packageName}.${controllerClass.name}" : controllerClass.name
                domainClass = application.getDomainClass(scaffoldProperty)
            }
        }
        return domainClass
    }

    private createScaffoldedInstance(GroovyClassLoader parentLoader, String controllerSource) {
        def classLoader = new GroovyClassLoader(parentLoader)
        def scaffoldedControllerClass = classLoader.parseClass(controllerSource)
        def scaffoldedInstance = scaffoldedControllerClass.newInstance()
        return scaffoldedInstance
    }

    private List getScaffoldedActions(scaffoldedInstance) {
        def actionProperties = scaffoldedInstance.metaClass.properties.collect {MetaProperty mp ->
            try {
                def val = mp.getProperty(scaffoldedInstance)
                if (val instanceof Closure) return mp
            }
            catch (Exception e) {
                // ignore
            }
        }
        return actionProperties
    }

    private String generateControllerSource(GrailsTemplateGenerator generator, GrailsDomainClass domainClass) {
        def sw = new StringWriter()
        println "Generating controller logic for scaffolding domain: ${domainClass.fullName}"
        generator.generateController(domainClass, sw)
        String controllerSource = sw.toString()
        return controllerSource
    }

}
